////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNMath/inc/WNCircle.h"
#include "WNMath/inc/WNBasicMath.h"
#include "WNMath/inc/WNBounds2.h"
#include "WNMath/inc/WNExtendedMath.h"
#include "WNMath/inc/WNRectangle.h"
#include "WNMath/inc/WNVector2.h"

using namespace WNMath;

// Constructors ////////////////////////////////////////////////////////////////////////////////////

WNCircle::WNCircle() :
    location(0.0f, 0.0f),
    radius(0.0f) {
}

WNCircle::WNCircle(const WN_FLOAT32* _f) :
    location(_f[0], _f[1]),
    radius(_f[2]) {
}

WNCircle::WNCircle(const WNPoint2& _location, WN_FLOAT32 _radius) :
    location(_location),
    radius(_radius) {
}

WNCircle::WNCircle(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _radius) :
    location(_x, _y),
    radius(_radius) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////////

WN_BOOL WNCircle::operator == (const WNCircle& _circle) const {
    return(0 == WNMemCmp(this, &_circle, sizeof(WNCircle)));
}

WN_BOOL WNCircle::operator != (const WNCircle& _circle) const {
    return(0 != WNMemCmp(this, &_circle, sizeof(WNCircle)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNCircle::Zero() {
    location.Zero();
    radius = 0.0f;
}

WN_VOID WNCircle::Set(const WN_FLOAT32* _f) {
    location.x = _f[0];
    location.y = _f[1];
    radius = _f[2];
}

WN_VOID WNCircle::Set(const WNPoint2& _location, WN_FLOAT32 _radius) {
    location = _location;
    radius = _radius;
}

WN_VOID WNCircle::Set(WN_FLOAT32 _x, WN_FLOAT32 _y, WN_FLOAT32 _radius) {
    location.x = _x;
    location.y = _y;
    radius = _radius;
}

WN_BOOL WNCircle::IsZero() const {
    return(location.IsZero() && radius == 0.0f);
}

WN_BOOL WNCircle::IsInsideOut() const {
    return(radius < 0.0f);
}

WN_FLOAT32 WNCircle::Circumference() const {
    return(static_cast<WN_FLOAT32>(WN_PI) * 2.0f * radius);
}

WN_FLOAT32 WNCircle::Area() const {
    return(static_cast<WN_FLOAT32>(WN_PI) * radius * radius);
}

WN_VOID WNCircle::Expand(WN_FLOAT32 _amount) {
    radius += _amount;
}

WN_BOOL WNCircle::Expand(const WNPoint2& _point, WN_BOOL _anchor) {
    if (radius < 0.0f) {
        if (!_anchor) {
            location = _point;
            radius = 0.0f;
        } else {
            radius = WNDistance(location, _point);
        }

        return(WN_TRUE);
    } else {
        const WN_FLOAT32 distanceSquared = WNDistanceSquared(location, _point);

        if (distanceSquared > radius * radius) {
            const WN_FLOAT32 distance = WNSqrt(distanceSquared);

            if (!_anchor) {
                location += (_point - location) * 0.5f * (1.0f - radius / distance);
                radius += 0.5f * (distance - radius);
            } else {
                radius = distance;
            }

            return(WN_TRUE);
        }
    }

    return(WN_FALSE);
}

WN_BOOL WNCircle::Expand(const WNRectangle& _rectangle, WN_BOOL _anchor) {
    WNPoint2 points[4];

    _rectangle.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor));
}

WN_BOOL WNCircle::Expand(const WNBounds2& _bounds, WN_BOOL _anchor) {
    WNPoint2 points[4];

    _bounds.GetPoints(points);

    return(Expand(points[0], _anchor) || Expand(points[1], _anchor) ||
           Expand(points[2], _anchor) || Expand(points[3], _anchor));
}

WN_BOOL WNCircle::Expand(const WNCircle& _circle, WN_BOOL _anchor) {
    if (radius < 0.0f) {
        if (!_anchor) {
            location = _circle.location;
            radius = _circle.radius;
        } else {
            radius = WNDistance(location, _circle.location) + _circle.radius;
        }

        return(WN_TRUE);
    } else {
        const WN_FLOAT32 distanceSquared = WNDistanceSquared(location, _circle.location);

        if (distanceSquared > (radius + _circle.radius) * (radius + _circle.radius)) {
            const WN_FLOAT32 distance = WNSqrt(distanceSquared);

            if (!_anchor) {
                location += (_circle.location - location) * 0.5f *
                            (1.0f - radius / (distance + _circle.radius));
                radius += 0.5f * ((distance + _circle.radius) - radius);
            } else {
                radius = distance;
            }

            return(WN_TRUE);
        }
    }

    return(WN_FALSE);
}

WN_VOID WNCircle::Translate(const WNVector2& _vector) {
    location += _vector;
}

WNCircle WNCircle::GetExpanded(WN_FLOAT32 _amount) const {
    WNCircle circle = *this;

    return(circle.Expand(_amount), circle);
}

WNCircle WNCircle::GetExpanded(const WNPoint2& _point, WN_BOOL _anchor) const {
    WNCircle circle = *this;

    return(circle.Expand(_point, _anchor), circle);
}

WNCircle WNCircle::GetExpanded(const WNRectangle& _rectangle, WN_BOOL _anchor) const {
    WNCircle circle = *this;

    return(circle.Expand(_rectangle, _anchor), circle);
}

WNCircle WNCircle::GetExpanded(const WNCircle& _circle, WN_BOOL _anchor) const {
    WNCircle circle = *this;

    return(circle.Expand(_circle, _anchor), circle);
}

WNCircle WNCircle::GetTranslated(const WNVector2& _vector) const {
    WNCircle circle = *this;

    return(circle.Translate(_vector), circle);
}